package cn.bqjr.eily.web.exception.resolver.impl;

import cn.bqjr.eily.core.constants.Constants;
import cn.bqjr.eily.web.exception.ErrorInfo;
import cn.bqjr.eily.web.exception.ExceptionMapping;
import cn.bqjr.eily.web.exception.exclass.BaseException;
import cn.bqjr.eily.web.exception.exclass.RequestException;
import cn.bqjr.eily.web.exception.resolver.IExceptionResolver;
import cn.bqjr.eily.web.utils.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.DefaultErrorAttributes;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;

/**
 * 默认异常解析器
 * Created by heshan on 2017/5/7.
 */
@EnableConfigurationProperties({ServerProperties.class})
public class DefaultExcepetionResolver extends DefaultErrorAttributes implements IExceptionResolver {

    @Autowired
    private ServerProperties serverProperties;

    /**
     * 获取异常实体
     * @param requestAttributes
     * @return
     */
    @Override
    public Throwable getError(RequestAttributes requestAttributes) {
        Throwable e = super.getError(requestAttributes);

        if (e instanceof ServletException) {
            e = e.getCause();
        } else if (e instanceof IllegalArgumentException) {
            e = new RequestException(e.getMessage(), e);
        }
        /**
        else if (e instanceof AuthorizationException) {
            e = new UnAuthorizedException(I18nUtils.getMsg("error.authorized.userUnauthorized"), e);
        }
         **/

        return e;
    }

    /**
     * 封装异常为ErrorInfo
     * @param request
     * @return
     */
    @Override
    public ErrorInfo resolve(HttpServletRequest request) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
        Map<String, Object> errorAttributes = this.getErrorAttributes(requestAttributes, isIncludeStackTrace(request));
        Throwable e = getError(requestAttributes);
        return convert(request, errorAttributes, e);
    }

    /**
     * 获取异常各项属性信息
     * @param requestAttributes request请求信息
     * @param includeStackTrace 是否包含异常堆栈信息
     * @return
     */
    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        this.addStatusAndError(errorAttributes, requestAttributes);
        return errorAttributes;
    }

    /**
     * 添加httpstatus和error信息
     *
     * @param errorAttributes
     * @param requestAttributes
     */
    protected void addStatusAndError(Map<String, Object> errorAttributes, RequestAttributes requestAttributes) {
        HttpStatus status = null;
        Integer defaultStatus = this.getAttribute(requestAttributes,
                org.springframework.web.util.WebUtils.ERROR_STATUS_CODE_ATTRIBUTE);
        if (null != defaultStatus) {
            try {
                status = HttpStatus.valueOf(defaultStatus.intValue());
            } catch (Exception e) {
            }
        }
        Throwable e = getError(requestAttributes);
        boolean isInstanceofBaseException = e instanceof BaseException;
        errorAttributes.put("status", this.getExceptionHttpstatus(e, isInstanceofBaseException, status).value());
        errorAttributes.put("message", this.getExceptionErrorMsg(e, isInstanceofBaseException, status));
        errorAttributes.put("errorCode", this.getExceptionErrorCode(e, isInstanceofBaseException));
    }

    /**
     * 获取Exception对应的httpstatus
     *
     * @param e
     * @return
     */
    private HttpStatus getExceptionHttpstatus(Throwable e, boolean isInstanceofBaseException, HttpStatus status) {
        if (isInstanceofBaseException) {
            return ((BaseException) e).getExceptionMapping().getHttpStatus();
        } else {
            //TODO 创建一个map，将Exception和errorMsg、status等信息建立映射，持久化在数据库，系统启动时进行初始化
            if (null != status) {
                return status;
            }
            return ExceptionMapping.DEFAUT_EXCEPTION.getHttpStatus();
        }
    }

    /**
     * 获取Exception对应的errorMsg
     *
     * @param e
     * @return
     */
    private String getExceptionErrorMsg(Throwable e, boolean isInstanceofBaseException, HttpStatus status) {
        if (isInstanceofBaseException) {
            return e.getMessage();
        } else {
            //TODO 创建一个map，将Exception和errorMsg、status等信息建立映射，持久化在数据库，系统启动时进行初始化
            if (null != status && !status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
                return status.getReasonPhrase();
            }
            return ExceptionMapping.DEFAUT_EXCEPTION.getErrorMsg();
        }
    }

    /**
     * 获取Exception对应的errorCode
     *
     * @param e
     * @return
     */
    private String getExceptionErrorCode(Throwable e, boolean isInstanceofBaseException) {
        if (isInstanceofBaseException) {
            return ((BaseException) e).getExceptionMapping().getErrorCode();
        } else {
            //TODO 创建一个map，将Exception和errorMsg、status等信息建立映射，持久化在数据库，系统启动时进行初始化
            return ExceptionMapping.DEFAUT_EXCEPTION.getErrorCode();
        }
    }

    private <T> T getAttribute(RequestAttributes requestAttributes, String name) {
        return (T) requestAttributes.getAttribute(name, 0);
    }

    /**
     * 是否包含异常堆栈
     *
     * @param request the source request
     * @return
     */
    private boolean isIncludeStackTrace(HttpServletRequest request) {
        ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return includeTraceParameter(request);
        }
        return false;
    }

    /**
     * 是否包含trace
     *
     * @param request
     * @return
     */
    private boolean includeTraceParameter(HttpServletRequest request) {
        String parameter = request.getParameter("trace");
        if (parameter == null) {
            return false;
        }
        return !Boolean.FALSE.toString().equals(parameter.toUpperCase());
    }

    /**
     * 将异常信息包装为ErrorInfo
     *
     * @param errorAttributes
     * @param e
     * @return
     */
    private ErrorInfo convert(HttpServletRequest request, Map<String, Object> errorAttributes, Throwable e) {
        ErrorInfo errorInfo = new ErrorInfo();
        errorInfo.setErrorMsg((String) errorAttributes.get("message"));
        errorInfo.setHttpStatus((Integer) errorAttributes.get("status"));
        errorInfo.setErrorCode((String) errorAttributes.get("errorCode"));
        errorInfo.setTimestamp((Date) errorAttributes.get("timestamp"));

        Object requestId = request.getAttribute(Constants.REQ_ID);
        if(null != requestId) {
            errorInfo.setRequestId(String.valueOf(requestId));
        }

        if (WebUtils.isDebugMode(request)) {
            errorInfo.setRequestURL((String) errorAttributes.get("path"));
            //errorInfo.setException(e);
        }
        return errorInfo;
    }
}
